#ifndef __PMU_H__
#define __PMU_H__

#include "board.h"

/* Number of counters */
#define ARM_PMU_CNTER_NR 4

enum rt_hw_pmu_event_type {
	ARM_PMU_EVENT_PMNC_SW_INCR      = 0x00,
	ARM_PMU_EVENT_L1_ICACHE_REFILL  = 0x01,
	ARM_PMU_EVENT_ITLB_REFILL       = 0x02,
	ARM_PMU_EVENT_L1_DCACHE_REFILL  = 0x03,
	ARM_PMU_EVENT_L1_DCACHE_ACCESS  = 0x04,
	ARM_PMU_EVENT_DTLB_REFILL       = 0x05,
	ARM_PMU_EVENT_MEM_READ          = 0x06,
	ARM_PMU_EVENT_MEM_WRITE         = 0x07,
	ARM_PMU_EVENT_INSTR_EXECUTED    = 0x08,
	ARM_PMU_EVENT_EXC_TAKEN         = 0x09,
	ARM_PMU_EVENT_EXC_EXECUTED      = 0x0A,
	ARM_PMU_EVENT_CID_WRITE         = 0x0B,
};

/* Enable bit */
#define ARM_PMU_PMCR_E   (0x01 << 0)
/* Event counter reset */
#define ARM_PMU_PMCR_P   (0x01 << 1)
/* Cycle counter reset */
#define ARM_PMU_PMCR_C   (0x01 << 2)
/* Cycle counter divider */
#define ARM_PMU_PMCR_D   (0x01 << 3)

#ifdef __GNUC__
rt_inline void rt_hw_pmu_enable_cnt(int divide64)
{
	unsigned long pmcr;
	unsigned long pmcntenset;

	asm volatile("mrc p15, 0, %0, c9, c12, 0" : "=r"(pmcr));
	pmcr |= ARM_PMU_PMCR_E | ARM_PMU_PMCR_P | ARM_PMU_PMCR_C;

	if(divide64)
		pmcr |= ARM_PMU_PMCR_D;
	else
		pmcr &= ~ARM_PMU_PMCR_D;

	asm volatile("mcr p15, 0, %0, c9, c12, 0" :: "r"(pmcr));

	/* enable all the counters */
	pmcntenset = ~0;
	asm volatile("mcr p15, 0, %0, c9, c12, 1" :: "r"(pmcntenset));
	/* clear overflows(just in case) */
	asm volatile("mcr p15, 0, %0, c9, c12, 3" :: "r"(pmcntenset));
}

rt_inline unsigned long rt_hw_pmu_get_control(void)
{
	unsigned long pmcr;
	asm("mrc p15, 0, %0, c9, c12, 0" : "=r"(pmcr));
	return pmcr;
}

rt_inline unsigned long rt_hw_pmu_get_ceid(void)
{
	unsigned long reg;
	/* only PMCEID0 is supported, PMCEID1 is RAZ. */
	asm("mrc p15, 0, %0, c9, c12, 6" : "=r"(reg));
	return reg;
}

rt_inline unsigned long rt_hw_pmu_get_cnten(void)
{
	unsigned long pmcnt;
	asm("mrc p15, 0, %0, c9, c12, 1" : "=r"(pmcnt));
	return pmcnt;
}

rt_inline void rt_hw_pmu_reset_cycle(void)
{
	unsigned long pmcr;

	asm volatile("mrc p15, 0, %0, c9, c12, 0" : "=r"(pmcr));
	pmcr |= ARM_PMU_PMCR_C;
	asm volatile("mcr p15, 0, %0, c9, c12, 0" :: "r"(pmcr));
	asm volatile("isb");
}

rt_inline void rt_hw_pmu_reset_event(void)
{
	unsigned long pmcr;

	asm volatile("mrc p15, 0, %0, c9, c12, 0" : "=r"(pmcr));
	pmcr |= ARM_PMU_PMCR_P;
	asm volatile("mcr p15, 0, %0, c9, c12, 0" :: "r"(pmcr));
	asm volatile("isb");
}

rt_inline unsigned long rt_hw_pmu_get_cycle(void)
{
	unsigned long cyc;
	asm volatile("isb");
	asm volatile("mrc  p15, 0, %0, c9, c13, 0" : "=r"(cyc));
	return cyc;
}

rt_inline void rt_hw_pmu_select_counter(int idx)
{
	RT_ASSERT(idx < ARM_PMU_CNTER_NR);

	asm volatile("mcr p15, 0, %0, c9, c12, 5" : : "r"(idx));
	/* Linux add an isb here, don't know why here. */
	asm volatile("isb");
}

rt_inline void rt_hw_pmu_select_event(int idx,
                                      enum rt_hw_pmu_event_type eve)
{
	RT_ASSERT(idx < ARM_PMU_CNTER_NR);

	rt_hw_pmu_select_counter(idx);
	asm volatile("mcr p15, 0, %0, c9, c13, 1" : : "r"(eve));
}

rt_inline unsigned long rt_hw_pmu_read_counter(int idx)
{
	unsigned long reg;

	rt_hw_pmu_select_counter(idx);
	asm volatile("isb");
	asm volatile("mrc p15, 0, %0, c9, c13, 2" : "=r"(reg));
	return reg;
}

rt_inline unsigned long rt_hw_pmu_get_ovsr(void)
{
	unsigned long reg;
	asm volatile("isb");
	asm("mrc  p15, 0, %0, c9, c12, 3" : "=r"(reg));
	return reg;
}

rt_inline void rt_hw_pmu_clear_ovsr(unsigned long reg)
{
	asm("mcr  p15, 0, %0, c9, c12, 3" : : "r"(reg));
	asm volatile("isb");
}

#endif

void rt_hw_pmu_dump_feature(void);

#endif /* end of include guard: __PMU_H__ */

